﻿/****************
 * Copyright 2016 Brian Tabone
 * Licensed under the Apache 2.0 License
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * PropertyManager.cs
 * Run time properties of this entity
 * 8/27/2016
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using IAO.Common.Network.EventArgs;
using IAO.Common.Network.MessageImplementations.SCADA;
using IAO.Common.ServiceFramework.DataAccessLayer;
using IAO.Common.ServiceFramework.RuntimeProperties;

namespace IAO.Common.ServiceFramework
{
    /// <summary>
    ///     Manage the collection of properties for the system
    /// </summary>
    public class PropertyManager
    {
        private static PropertyManager _singleton;
        private readonly List<Property> _properties;
        private IAODal _dal;

        private PropertyManager()
        {
            _properties = new List<Property>();

            Console.WriteLine($"Properties Singleton Created");
        }

        /// <summary>
        ///     Get the property manager singleton. Initial callers should
        /// </summary>
        public static PropertyManager GetPropertyManager
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get
            {
                if (_singleton == null)
                {
                    _singleton = new PropertyManager();
                }

                return _singleton;
            }
        }

        /// <summary>
        ///     Set the data access layer for persistent storage of properties.
        /// </summary>
        /// <param name="dal"></param>
        public void SetDataAccessLayer(IAODal dal)
        {
            _dal = dal;
            LoadPersistentProperties();
        }


        /// <summary>
        ///     Handle set properties events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void PropertiesController_SetPropertiesEvent(object sender, SetPropertyEventArgs args)
        {
            var propertyToSet = _properties.Find(pr => pr.Id == args.PropertyID);
            propertyToSet.SetValue(args.NewValue);

            if (_dal != null)
            {
                _dal.UpdateProperty(args.PropertyID, args.NewValue);
            }
        }

        /// <summary>
        ///     Get the properties for this service
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public List<PropertyMessage> PropertiesController_GetPropertiesEvent(object sender, EventArgs e)
        {
            var propertyMessages = new List<PropertyMessage>();
            var properties = _properties;
            foreach (var prop in properties.OrderBy(pr => pr.Id))
            {
                propertyMessages.Add(prop.Message());
            }
            return propertyMessages;
        }

        /// <summary>
        ///     Load the properties that are stored in the database, typically only called at startup
        /// </summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void LoadPersistentProperties()
        {
            if (null != _properties.FirstOrDefault(pr => pr.GetType() != typeof(VolatileProperty)))
            {
               throw new ArgumentException("Database properties should be loaded before any new persistent properties have been added");
            }

            _properties.AddRange(_dal.GetAllNonVolatileProperties());
        }

        /// <summary>
        ///     Add a property to the collection
        /// </summary>
        /// <param name="newProp"></param>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void AddProperty(Property newProp)
        {
            var existingProperty = _properties.Find(pr => pr.Id == newProp.Id);
            if (null != existingProperty)
            {
                throw new ArgumentException(
                    $"Property already exists: {existingProperty.Id}, {existingProperty.Name}, {existingProperty.Value}");
            }

            _properties.Add(newProp);

            if (_dal != null && !(newProp is VolatileProperty))
            {
                if (!_dal.PropertyExists(newProp.Id))
                {
                    _dal.AddProperty(newProp.Id, newProp.Class, newProp.Name, newProp.Value, newProp.Type, newProp.ReadOnly);
                }
            }
        }

        /// <summary>
        ///     Delete a property from the collection
        /// </summary>
        /// <param name="removeProp">The property to be removed from the collection</param>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void DeleteProperty(Property removeProp)
        {
            _properties.Remove(_properties.Find(pr => pr.Id == removeProp.Id));

            // If the property is persistent, remove it from the db
            if (!(removeProp is VolatileProperty))
            {
                // _dal.RemoveProperty(removeProp.Id);
            }
        }

        /// <summary>
        ///     Add persistent properties (only if they don't already exist in the database). Callers should have called LoadPersistentProperties before this    
        /// </summary>
        /// <param name="newProp"></param>
        /// <returns></returns>
        public bool AddPersistentPropertyIfNotExists(Property newProp)
        {
            if (newProp is VolatileProperty)
            {
                throw new ArgumentException("Volatile properties must be added by AddProperty, this method is for persistent properties only");
            }

            bool added = false;

            if (GetProperty(newProp.Id) == null)
            {
                AddProperty(newProp);
                added = true;
            }
            return added;
        }

        /// <summary>
        ///     Get a property by ID
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Property GetProperty(int id)
        {
            // There should only be one so use first or default
            return _properties.FirstOrDefault(pr => pr.Id == id);
        }
    }
}